注意:所有文章除特别说明外,转载请注明出处.
[TOC]
第二章 深入分析Java IO的工作机制
2.1 Java 的I/O库的基本架构
IO问题是整个人机交互的核心问题,因为IO是机器获取和交换信息的重要渠道。Java的io类都在类java.io下,这些io类可以分为四组:
传输数据的数据格式:
1. 基于字节操作的IO接口:InputStream | OutputStream
2. 基于字符操作的IO接口:Writer | Reader
传输数据的方式:
3. 基于磁盘操作的IO接口:File
4. 基于网络操作的IO接口:Socket
2.1.1 基于字节的IO操作接口
1. 操作数据的方式是可以组合使用的
OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream(new FileOutputStream("fileName"))));
2. 流最终写到什么地方必须要指定,要么写到磁盘,要么写到网络中
2.1.2 基于字符的IO操作接口
1. write(char[] buf, int off, int len);
2. int read(char[] buf, int off, int len);
2.1.3 字节与字符的转换接口
数据持久化与网络传输都是以字节进行的,所以必须要有字符和字节之间的转换。
1. InputStreamReader 字节到字符的转换桥梁,InputStream到Reader的过程需要指定编码字符集,否则采用默认编码,可能出现乱码。
2.2 磁盘IO工作机制
2.2.1 几种访问文件的方式
1.标准访问文件方式
2.直接IO方式
3.同步访问文件方式
数据的写入和读取都是同步的,它与标准访问方式不一样的是,只有当数据被成功写到磁盘时才返回给应用程序成功标志。
4.异步访问文件方式
2.2.2 Java访问磁盘文件
2.2.3 Java序列化
2.3 网络IO工作机制
2.3.1 TCP状态转换
2.3.5 数据传输
当连接建立成功之后,服务端和客户端都会拥有一个socket实例,每个socket实例都有一个inputStream和outputStream,通过这两个对象来交换数据。
2.4 NIO的工作方式
2.4.1 BIO的挑战
BIO(阻塞IO),不管是磁盘IO还是网络IO,数据在写入OutputStream或者从InputStream读取时都会可能阻塞,一旦阻塞,线程将会失去CPU使用权,这在当前的大规模访问量和有性能要求的情况下是不能被接受的。
2.4.2 NIO的工作机制
NIO的两个核心概念,Channel | Selector。
1.Channel:channel要比socket更加具体,其可以体现为一种具体的交通工具。如:汽车或高铁。
2.Selector:其可以看做是一个车站的车站运行调度系统,将负责监控车辆的当前运行状态(在路上或者出站等)。可以轮询每个Channel的状态。
3. Buffer类:它比Stream更加具体,如果Channel是汽车的话,那么Buffer就是汽车上的具体座位,它始终是一个具体的概念。在Buffer中我们可以控制Buffer的容量、是否扩容以及如何扩容。
2.4.3 Buffer的工作方式
在Selector检测到通信信道IO有数据传输时,通过select()取得SocketChannel,将数据读取或写入Buffer缓冲区。
Buffer可以简单的理解为一组基本数据类型的元素列表。
1. capacity 缓冲区数组总长度
2. position 下一个要操作数据元素的位置
3. limit 缓冲区数组中不可操作的下一个元素位置
4. mark 记录当前position的前一个位置或默认是0
ByteBuffer.allocate(n),创建长度n的数组缓冲区。
要将字节数据写入Channel通信信道,调用Buffer.flip()方法。
2.4.4 NIO数据访问方式
NIO提供比传统文件方式访问的更好的方式。
1. FileChannel.transferTo
2. FileChannel.transferFrom
上面两种方式与传统访问文件方式相比可以减少数据从内核到用户空间的复制。
3. FileChannel.map
上面的方式将文件按照一定大小块映射为内存区域,当程序访问这个内存区域时将直接操作这个文件数据,这种方式省去数据从内核空间向用户空间复制的损耗。
提示:NIO引入Channel | Buffer | Selector就是想将这些信息具体化,让我们有机会控制它们。
2.5 IO调优
2.5.1 磁盘IO优化
1. 性能检测
2.6 设计模式之适配器模式
适配器模式是将一个类的接口变换成客户端所能接受的另一种接口,从而使得两个接口不匹配而无法在一起工作的两个类能够在一起工作。如在项目中需要引用一些开源框架来一起工作的情况下需要引入适配器模式。
适配器模式的结构:
1. Target 目标接口,所需要转换的所期待的接口
2. Adaptee 源角色,需要适配的接口
3. Adapter 适配器,将源接口适配成目标接口,继承源接口,实现目标接口
2.6.2 Java IO中适配器模式
适配器的作用是将一个接口适配到另一个接口,在Java IO类库中有很多这样的需求。如将字符串数据转变成字节数据保存到文件中,将字节数据转变成流数据等。
2.7 设计模式之装饰器模式
装饰器模式的构建:
1. Component 抽象组件角色,定义一组抽象的接口,规定被装饰器组件都有哪些功能。
2. ConcreteComponent 实现抽象组件的所有功能
3. Decorator 装饰器角色,持有一个Component对象实例的引用,定义一个与抽象组件一直的接口。
4. ConcreteDecorator 具体的装饰器实现者,负责实现装饰器角色定义的功能
2.7.2 Java IO 装饰器模式
2.8 适配器模式与装配器模式的区别
适配器模式的意义在于将一个接口转变成另一个子接口。而装配器模式不是要改变被装饰对象的接口,而恰恰要保持原有的接口,但是增强原有对象的功能。或者改变原有对象的处理方式而提升性能。